.. _глава_77_start:

Кортежи
========

.. index::
    tuple
    кортеж
    функция tuple()
    function tuple()

Кортежи предназначены для объединения нескольких значений в один объект. Они
реализованы как библиотечная функция шаблоном Tuple из модуля std.typecons.
Tuple использует AliasSeq из модуля std.meta для некоторых своих операций.
операций.
В этой главе рассматриваются только наиболее распространенные операции с кортежами.
Для получения дополнительной информацию о кортежах и шаблонах можно найти в книге Филиппа Сиго "D Templates: A Tutorial1


Кортеж и функция tuple()
------------------------

Кортежи обычно строятся с помощью удобной функции tuple():

.. code-block:: d

    import std.stdio;

    import std.typecons;

    void main()
    {
        auto t = tuple(42, "hello");
        writeln(t);
    }

Приведенный выше вызов кортежа создает объект, состоящий из значения int 42 и
строкового значения "hello". В выводе программы указан тип объекта
кортежа и его членов:

.. code-block:: console

Tuple!(int, string)(42, "hello")

Приведенный выше тип кортежа эквивалентен следующему определению псевдоструктуры
и, вероятно, был реализован точно таким же образом:

.. code-block:: d

    // Эквивалент Tuple!(int, string)
    struct __Tuple_int_string
    {
        int __member_0;
        string __member_1;
    }

Обычно доступ к членам кортежа осуществляется по значениям их индексов. Такой синтаксис
предполагает, что кортежи можно рассматривать как массивы, состоящие из различных типов
элементов:

.. code-block:: d

    writeln(t[0]);
    writeln(t[1]);

Выходные данные:

.. code-block:: d

    42
    hello

**Свойства членов**

Доступ к членам по свойствам возможен, если шаблоны tuple и Tuple
инстанцированы с именами членов. Оба приведенных ниже метода имеют одинаковый
эффект:

.. code-block:: d

    auto a = tuple!("number", "message")(42, "hello");
    auto b = Tuple!(int, "number", string, "message")(42, "hello");

Приведенные выше определения позволяют обращаться к членам по свойствам .number и .message
свойствам:

.. code-block:: d

    writeln("by index 0 : ", a[0]);
    writeln("by .number : ", a.number);
    writeln("by index 1 : ", a[1]);
    writeln("by .message: ", a.message);


Вывод:

    by index 0 : 42
    by .number : 42
    by index 1 : hello
    by .message: hello

**Развертывание членов в виде списка значений**

Члены кортежа можно раскрыть в виде списка значений, который можно использовать, например, в качестве
список аргументов при вызове функции. Члены могут быть расширены либо с помощью
свойство .expand или с помощью фрагментов:

.. code-block:: d

    import std.stdio;
    import std.typecons;

    void foo(int i, string s, double d, char c)
    {
        // ...
    }

    void bar(int i, double d, char c)
    {
        // ...
    }

    void main()
    {
        auto t = tuple(1, "2", 3.3, '4');
        // Both of the following lines are equivalents of
        // foo(1, "2", 3.3, '4'):
        foo(t.expand);
        foo(t[]);
        // The equivalent of bar(1, 3.3, '4'):
        bar(t[0], t[$ - 2 .. $]);
    }

Приведенный выше кортеж состоит из четырех значений int, string, double и char. Поскольку
эти типы соответствуют списку параметров foo(), расширение его членов можно
использовать в качестве аргументов для foo(). При вызове bar() соответствующий список аргументов
состоит из первого члена и двух последних членов кортежа.
До тех пор, пока члены совместимы и являются элементами одного и того же массива, расширение кортежа
расширение кортежа можно использовать и в качестве значений элементов литерала массива:

.. code-block:: d

    import std.stdio;
    import std.typecons;

    void main()
    {
        auto t = tuple(1, 2, 3);
        auto a = [t.expand, t[]];
        writeln(a);
    }

Приведенный выше литерал массива инициализируется путем двукратного расширения одного и того же кортежа:

.. code-block:: console

    [1, 2, 3, 1, 2, 3]

.. index::
    compile-time foreach
    foreach времени компиляции

**foreach времени компиляции**

Поскольку их значения могут быть расширены, кортежи можно использовать в операторе foreach
также и с оператором foreach:

.. code-block:: d

    auto t = tuple(42, "hello", 1.5);
    foreach (i, member; t)
    {
        writefln("%s: %s", i, member);
    }

Вывод:

.. code-block:: d

    0: 42
    1: hello
    2: 1.5

Приведенный выше оператор foreach может создать ложное впечатление: Можно подумать, что это
что это цикл, который выполняется во время выполнения. Это не так. Скорее, оператор foreach
который работает с членами кортежа, представляет собой разворачивание тела цикла
для каждого члена. Приведенный выше оператор foreach эквивалентен следующему
код:

.. code-block:: d


    {
        enum size_t i = 0;
        int member = t[i];
        writefln("%s: %s", i, member);
    }
    {
        enum size_t i = 1;
        string member = t[i];
        writefln("%s: %s", i, member);
    }
    {
        enum size_t i = 2;
        double member = t[i];
        writefln("%s: %s", i, member);
    }

Причина разворачивания заключается в том, что когда члены кортежа имеют
разных типов, тело цикла foreach должно быть скомпилировано по-разному для каждого типа.
Мы познакомимся со статическим foreach, более мощной функцией разворачивания цикла, в одной из следующих глав (:ref:`static foreach<глава_83_start>`).

**Возврат нескольких значений из функций**

Кортежи могут стать простым решением проблемы ограничения, связанного с тем, что функции должны возвращать
одно значение. Примером может служить std.algorithm.findSplit. findSplit()
ищет диапазон внутри другого диапазона и выдает результат, состоящий из
трех частей: части до найденного диапазона, найденного диапазона и части после
найденного диапазона:

.. code-block:: d

    import std.algorithm;

    // ...
    auto entireRange = "hello";
    auto searched = "ll";
    auto result = findSplit(entireRange, searched);
    writeln("before: ", result[0]);
    writeln("found : ", result[1]);
    writeln("after : ", result[2])

Вывод:


.. code-block:: console

    before: he
    found : ll
    after : o

Другой вариант возврата нескольких значений из функции - возврат объекта
объект struct:

.. code-block:: d

    struct Result
    {
    // ...
    }
    Result foo()
    {
    // ...
    }

.. index::
    AliasSeq
    возврат нескольких значений

AliasSeq
--------

AliasSeq определен в модуле std.meta. Он используется для представления понятия
которое обычно используется компилятором, но иначе недоступно для
программисту как сущность: Список значений, типов и символов, разделенных запятыми
(т. е. аргументов шаблона псевдонимов). Ниже приведены три примера таких списков:

* Список аргументов функции
* Список аргументов шаблона
* Список элементов литералов массива

Следующие три строки кода являются примерами этих списков в том же порядке:

.. code-block:: d

    foo(1, "hello", 2.5); // function arguments
    auto o = Bar!(char, long)(); // template arguments
    auto a = [ 1, 2, 3, 4 ]; // array literal elements

Tuple использует преимущества AliasSeq при расширении своих членов.
Название AliasSeq происходит от "последовательность псевдонимов", и он может содержать типы,
значения и символы. (AliasSeq и std.meta раньше назывались TypeTuple и
std.typetuple, соответственно.)
В этой главе приведены примеры AliasSeq, которые состоят только из типов или только из
значений. Примеры его использования как с типами, так и со значениями будут приведены в следующей главе.
главе. AliasSeq особенно полезен при работе с переменными шаблонами, которые мы рассмотрим
в следующей главе.

**AliasSeq, состоящий из значений**

Значения, которые представляет AliasSeq, указываются в качестве аргументов его шаблона.
Представим себе функцию, которая принимает три параметра:

.. code-block:: d

    import std.stdio;
    void foo(int i, string s, double d)
    {
        writefln("foo is called with %s, %s, and %s.", i, s, d);
    }

Обычно эта функция вызывается с тремя аргументами:

.. code-block:: d

    foo(1, "hello", 2.5);

AliasSeq может объединять эти аргументы как единое целое и автоматически
расширяться при вызове функций:

.. code-block:: d

    import std.meta;
    // ...
    alias arguments = AliasSeq!(1, "hello", 2.5);
    foo(arguments);

Хотя кажется, что функция теперь вызывается с одним аргументом, приведенный выше вызов
foo() эквивалентен предыдущему вызову. В результате оба вызова
приводят к одному и тому же результату:

.. code-block:: console

    foo is called with 1, hello, and 2.5.

Также обратите внимание, что arguments не определяется как переменная auto. Скорее, это
псевдоним конкретного экземпляра AliasSeq. Хотя можно определить
переменные AliasSeq, в примерах этой главы они будут использоваться только в качестве
псевдонимов.
Как мы видели выше на примере Tuple, когда значения совместимы и являются
элементами одного и того же массива, AliasSeq можно использовать для инициализации литерала массива:

.. code-block:: d

    alias elements = AliasSeq!(1, 2, 3, 4);
    auto arr = [ elements ];
    assert(arr == [ 1, 2, 3, 4 ]);

**Индексирование и разбиение**

Как и в случае с Tuple, доступ к членам AliasSeq может осуществляться с помощью индексов и фрагментов:

.. code-block:: d

    alias arguments = AliasSeq!(1, "hello", 2.5);
    assert(arguments[0] == 1);
    assert(arguments[1] == "hello");
    assert(arguments[2] == 2.5);

Предположим, что существует функция с параметрами, совпадающими с двумя последними членами
приведенного выше AliasSeq. Эту функцию можно вызвать с помощью фрагмента, состоящего только из двух последних
членов AliasSeq:

.. code-block:: d

    void bar(string s, double d)
    {
    // ...
    }
    // ...
    bar(arguments[$-2 .. $]);

**AliasSeq, состоящий из типов**

Члены AliasSeq могут состоять из типов. Другими словами, не конкретное значение
определенного типа, а сам тип, например int. AliasSeq, состоящий из типов, может
представлять аргументы шаблона.
Давайте используем AliasSeq с шаблоном struct, который имеет два параметра. Первый параметр
первый параметр этого шаблона определяет тип элемента массива-члена
а второй параметр определяет возвращаемое значение функции-члена:

.. code-block:: d

    import std.conv;

    struct S(ElementT, ResultT)
    {
        ElementT[] arr;
        ResultT length()
        {
            return to!ResultT(arr.length);
        }
    }

    void main()
    {
        auto s = S!(double, int)([1, 2, 3]);
        auto l = s.length();
    }

В приведенном выше коде мы видим, что шаблон инстанцируется с помощью (double, int).
AliasSeq также может представлять тот же список аргументов:

.. code-block:: d

    import std.meta;
    // ...
    alias Types = AliasSeq!(double, int);
    auto s = S!Types([ 1, 2, 3 ]);

Хотя кажется, что это один аргумент шаблона, Types расширяется
автоматически, и инстанцирование шаблона становится S!(double, int), как и раньше.
как и раньше.
AliasSeq особенно полезен в вариативных шаблонах. Мы увидим примеры этого
в следующей главе.

**foreach с AliasSeq**

Как и в случае с Tuple, оператор foreach, работающий с AliasSeq, не является циклом времени выполнения.
Скорее, это разворачивание тела цикла для каждого члена.
Давайте посмотрим пример этого на примере модульного теста, написанного для структуры S, которая была определена выше.
определена выше. Следующий код проверяет S на типы элементов int, long и float
(ResultT в этом примере всегда size_t):

.. code-block:: d

    unittest
    {
        alias Types = AliasSeq!(int, long, float);
        foreach (Type; Types)
        {
            auto s = S!(Type, size_t)([Type.init, Type.init]);
            assert(s.length() == 2);
        }
    }

Переменная Type в foreach соответствует int, long и float, в этом порядке. В результате
В результате оператор foreach компилируется как эквивалент кода, приведенного ниже:

.. code-block:: d

    {
        auto s = S!(int, size_t)([ int.init, int.init ]);
        assert(s.length() == 2);
    }
    {
        auto s = S!(long, size_t)([ long.init, long.init ]);
        assert(s.length() == 2);
    }
    {
        auto s = S!(float, size_t)([ float.init, float.init ]);
        assert(s.length() == 2);
    }

.. index::
    tupleof

Свойство .tupleof
-----------------

Свойство .tupleof представляет члены типа или объекта. При применении к пользовательскому
определенному пользователем типу, .tupleof обеспечивает доступ к определениям членов этого
типа:

.. code-block:: d

    import std.stdio;

    struct S
    {
        int number;
        string message;
        double value;
    }

    void main()
    {
        foreach (i, MemberType; typeof(S.tupleof))
        {
            writefln("Member %s:", i);
            writefln("  type: %s", MemberType.stringof);
            string name = S.tupleof[i].stringof;
            writefln("  name: %s", name);
        }
    }

S.tupleof появляется в программе в двух местах. Во-первых, типы элементов
получаются путем применения typeof к .tupleof, так что каждый тип появляется в виде переменной
MemberType. Во-вторых, имя члена получается следующим образом
S.tupleof[i].stringof.

Переменная в цикле foreach представляет каждый член объекта:

.. code-block:: console

    Member 0:
        type : int
        value: 42
    Member 1:
        type : string
        value: hello
    Member 2:
        type : double
        value: 1.5

Здесь важно отметить, что кортеж, который возвращает .tupleof, состоит
из самих членов объекта, а не из их копий. Другими словами,
члены кортежа являются ссылками на реальные члены объекта


Резюме
------

* tuple() объединяет различные типы значений подобно объекту struct.
* Доступ к членам можно получить через свойства
* Члены могут быть расширены как список значений с помощью .expand или с помощью фрагментов.
* foreach с кортежем не является циклом времени выполнения; скорее, это разворачивание цикла.
* AliasSeq представляет такие понятия, как список аргументов функции, список аргументов шаблона
  список, список аргументов шаблона, список элементов литерала массива и т. д.
* AliasSeq может состоять из значений и типов.
* Кортежи поддерживают индексацию и разбиение на части.
* .tupleof предоставляет информацию о членах типов и объектов.
